package com.seewo.dubbo.debugger.admin.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.seewo.dubbo.debugger.admin.common.validate.group.Query;
import com.seewo.dubbo.debugger.admin.common.validate.group.Update;
import com.seewo.dubbo.debugger.admin.entity.*;
import com.seewo.dubbo.debugger.admin.query.DebugQuery;
import com.seewo.dubbo.debugger.admin.service.*;
import com.seewo.dubbo.debugger.admin.utils.CommonUtil;
import com.seewo.dubbo.debugger.admin.utils.VelocityUtil;
import com.seewo.dubbo.debugger.api.Result;
import com.seewo.dubbo.debugger.api.model.ModelDef;
import com.seewo.dubbo.debugger.api.model.ParamDef;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
import org.jfree.data.category.DefaultCategoryDataset;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author chenliangliang
 * @date 2018/7/25
 */
@RestController
@RequestMapping("/base")
public class BaseController {

    @Autowired
    private IRegistryService registryService;

    @Autowired
    private IInterfaceService interfaceService;

    @Autowired
    private IMethodService methodService;

    @Autowired
    private IParameterService parameterService;

    @Autowired
    private IDebugService debugService;

    @Autowired
    private IServiceService serviceService;

    @Autowired
    private ThreadPoolTaskExecutor executor;

    private Semaphore semaphore = new Semaphore(2);

    /**
     * 获取注册中心信息
     *
     * @return 注册中心列表
     */
    @GetMapping("/registry/list")
    public Result listRegistry() {
        return Result.ok(registryService.selectList(null));
    }

    /**
     * 查询某个注册中心下的服务列表
     *
     * @param registry 注册中心：cProtocol，cAddress，protocolFilter（可选）
     * @return 服务列表
     */
    @GetMapping("/service/list")
    public Result listService(@Validated(Query.class) Registry registry) {
        return Result.ok(registryService.getServiceList(registry));
    }

    /**
     * 查询某个服务下的接口列表
     *
     * @param service 服务：cRegistryId，cProtocol，cAddress
     * @return 接口列表
     */
    @GetMapping("/interface/list")
    public Result listInterface(@Validated(Query.class) Service service) {
        return Result.ok(serviceService.getInterfaceList(service));
    }

    /**
     * 查询某个接口下的方法列表
     *
     * @param anterface 接口：cServiceId，cName
     * @return 方法列表
     */
    @GetMapping("/method/list")
    public Result listMethod(@Validated({Query.class}) Interface anterface) {
        return Result.ok(interfaceService.getMethods(anterface));
    }

    /**
     * 查询某个方法的具体信息
     *
     * @param method 方法：cInterfaceId，cName
     * @return api信息
     */
    @GetMapping("/api")
    public Result api(@Validated(Query.class) Method method) {
        return Result.ok(methodService.getApi(method));
    }


    /**
     * 更新注册中心名
     *
     * @param registry 注册中心：cId，cName
     * @return 成功或失败
     */
    @PutMapping("/registry/name")
    public Result updateRegistryName(@Validated(Update.class) Registry registry) {
        return Result.result(registryService.updateById(registry), "", null);
    }

    /**
     * 更新服务描述
     *
     * @param service 服务：cId，cDesc
     * @return 成功或失败
     */
    @PutMapping("/service/desc")
    public Result updateServiceDesc(@Validated(Update.class) @RequestBody Service service) {
        serviceService.updateDesc(service);
        return Result.ok();
    }

    /**
     * 更新接口描述
     *
     * @param anterface 接口：cId，cDesc
     * @return 成功或失败
     */
    @PutMapping("/interface/desc")
    public Result updateInterfaceDesc(@Validated(Update.class) @RequestBody Interface anterface) {
        interfaceService.updateDesc(anterface);
        return Result.result(interfaceService.updateById(anterface), "", null);
    }


    /**
     * 更新方法描述
     *
     * @param method 方法：cId，cDesc
     * @return 成功或失败
     */
    @PutMapping("/method/desc")
    public Result updateMethodDesc(@Validated(Update.class) @RequestBody Method method) {
        methodService.updateDesc(method);
        return Result.ok();
    }

    /**
     * 更新参数描述
     *
     * @param parameter 方法：cId，cDesc
     * @return 成功或失败
     */
    @PutMapping("/parameter/desc")
    public Result updateParameterDesc(@Validated(Update.class) @RequestBody Parameter parameter) {
        return Result.result(parameterService.updateById(parameter), "", null);
    }


    /**
     * 接口在线调试
     *
     * @param debugQuery 调试参数
     * @return 调试结果
     */
    @PostMapping("/debug")
    public Result debug(@RequestBody DebugQuery debugQuery) {
        return methodService.debug(debugQuery);
    }


    @GetMapping("/markdown")
    public void downloadDoc(@Validated({Query.class}) Interface anterface, HttpServletResponse response) throws IOException {
        try {
            //最多只允许两个线程同时访问
            semaphore.acquire();

            Interface anInterface = interfaceService.getMethods(anterface);
            List<Method> methods = anInterface.getMethods();
            int size = methods.size();
            List<Method> methodList = new ArrayList<>((int) (size * 1.5) + 1);
            CountDownLatch latch = new CountDownLatch(size);
            for (Method method : methods) {
                executor.execute(() -> {
                    methodList.add(methodService.getApi(method));
                    latch.countDown();
                });
            }
            //等待结果汇总
            latch.await(4, TimeUnit.SECONDS);
            anInterface.setMethods(methodList);

            for (Method method : methodList) {
                List<ParamDef> requestParams = method.getRequestParams();
                for (ParamDef paramDef : requestParams) {
                    ModelDef modelDef = paramDef.getModelDef();
                    if (modelDef != null) {
                        modelDef.getPropertyList().forEach(p -> {
                            p.setMethodId(null);
                            p.setDescription(null);
                        });
                        paramDef.setModelJson(JSON.toJSONString(modelDef, true));
                    }
                }
                ParamDef responseParam = method.getResponseParam();
                List<ParamDef> responseParams = Collections.singletonList(responseParam);
                String responseJson = CommonUtil.parse2ResponseJson(responseParams);
                responseParam.setResponseJson(CommonUtil.formatJson(responseJson));
            }

            Map<String, Object> data = new HashMap<>(4);
            data.put("interface", anInterface);
            data.put("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            response.setHeader("content-disposition", "attachment;filename=api-doc.md");
            VelocityUtil.generateMarkdown(data, "md/doc.md.vm", response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
            response.setContentType("application/text");
            response.getWriter().write("服务器繁忙，请稍后重试");
        } finally {
            semaphore.release();
        }
    }


    @GetMapping("/chart")
    public void jFreeChart(@RequestParam("methodId") Integer methodId, HttpServletResponse response) throws IOException {
        Method method = methodService.selectById(methodId);
        if (method == null) {
            return;
        }
        Page<Debug> page = new Page<>(1, 30);
        page.setOrderByField("c_id");
        page.setAsc(false);
        Page<Debug> pageInfo = debugService.selectPage(page, new EntityWrapper<Debug>().eq("c_method_id", methodId));
        List<Debug> records = pageInfo.getRecords();

        toChart(method, records, response.getOutputStream());
    }

    private void toChart(Method method, List<Debug> records, OutputStream outputStream) throws IOException {
        String title = method.getCName();
        DefaultCategoryDataset dataset = new DefaultCategoryDataset();
        System.out.println(records.size());
        BarRenderer barRenderer = new BarRenderer();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

        for (int i = 1, l = records.size(); i <= l; i++) {
            Debug debug = records.get(i - 1);
            String cTime = debug.getCTime();
            boolean flag = debug.getCResult() == 1;
            if (flag) {
                barRenderer.setSeriesPaint(0, Color.GREEN);
                dataset.addValue(Integer.valueOf(cTime), "成功", sdf.format(debug.getCCreateTime()));
            } else {
                barRenderer.setSeriesPaint(1, Color.RED);
                dataset.addValue(Integer.valueOf(cTime), "失败", sdf.format(debug.getCCreateTime()));
            }
        }

        StandardChartTheme mChartTheme = new StandardChartTheme("CN");
        ChartFactory.setChartTheme(mChartTheme);
        JFreeChart chart = ChartFactory.createBarChart(title,
                "请求时刻", "响应时间：ms", dataset, PlotOrientation.VERTICAL, true, true, false);

        CategoryPlot categoryPlot = chart.getCategoryPlot();
        ValueAxis rangeAxis = categoryPlot.getRangeAxis();
        CategoryAxis domainAxis = categoryPlot.getDomainAxis();
        // 设置Y轴的提示文字样式
        rangeAxis.setLabelFont(new Font("微软雅黑", Font.PLAIN, 12));
        // 设置Y轴刻度线的长度
        rangeAxis.setTickMarkInsideLength(10f);

        // 设置X轴下的标签文字
        domainAxis.setLabelFont(new Font("微软雅黑", Font.PLAIN, 12));
        // 设置X轴上提示文字样式
        domainAxis.setTickLabelFont(new Font("微软雅黑", Font.PLAIN, 8));
        domainAxis.setUpperMargin(0.001);
        domainAxis.setLowerMargin(0.001);
        domainAxis.setCategoryMargin(0.001);
        NumberAxis vn = (NumberAxis) categoryPlot.getRangeAxis();

        // 设置Y轴的数字为百分比样式显示
        vn.setRange(0, 1000);
        DecimalFormat df = new DecimalFormat("0");
        vn.setNumberFormatOverride(df);
        // 设置柱状图的顶端显示数字
        barRenderer.setIncludeBaseInRange(true);
        barRenderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
        barRenderer.setDefaultItemLabelsVisible(true);
        barRenderer.setMaximumBarWidth(0.005);
        // 设置柱子为平面图不是立体的
        barRenderer.setBarPainter(new StandardBarPainter());
        // 设置柱状图之间的距离0.1代表10%；
        barRenderer.setItemMargin(0.001);
        // 设置柱子的阴影，false代表没有阴影
        barRenderer.setShadowVisible(false);
        // 设置图的背景为白色
        categoryPlot.setBackgroundPaint(Color.WHITE);
        // 设置背景虚线的颜色
        categoryPlot.setRangeGridlinePaint(Color.decode("#B6A2DE"));
        // 去掉柱状图的背景边框，使边框不可见
        categoryPlot.setOutlineVisible(false);
        // 设置标题的字体样式
        chart.getTitle().setFont(new Font("微软雅黑", Font.PLAIN, 20));
        // 设置图表下方图例上的字体样式
        chart.getLegend().setItemFont(new Font("微软雅黑", Font.PLAIN, 12));
        categoryPlot.setRenderer(barRenderer);

        ChartUtils.writeChartAsJPEG(outputStream, chart, 600, 400);
    }
}
